/*
 * Copyright 2011-2012, David George, Licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.magneato.infra;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.magneato.dto.PageACL;
import org.magneato.service.Repository;
import org.magneato.utils.NodePermission;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.acls.domain.AccessControlEntryImpl;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.CumulativePermission;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.AccessControlEntry;
import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.AclCache;
import org.springframework.security.acls.model.AclService;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.Sid;
import org.springframework.util.Assert;

/**
 * The simplest possible implementation of AclService interface. Uses in-memory
 * collection of ACLs, providing fast and easy access to them.
 * 
 * @author David George
 * @date 29/Apr/2011
 */
public class InMemoryAclServiceImpl implements AclService,
		ApplicationContextAware {
	// @Autowired
	private Repository repository;

	private ApplicationContext applicationContext;

	@Autowired
	AclCache cache;

	private final Log _logger = LogFactory.getLog(InMemoryAclServiceImpl.class);

	public Acl readAclById(ObjectIdentity object, List<Sid> sids)
			throws NotFoundException {

		ArrayList<ObjectIdentity> objects = new ArrayList<ObjectIdentity>();
		objects.add(object);

		Map<ObjectIdentity, Acl> map = readAclsById(objects, sids);
		Assert.isTrue(map.containsKey(object),
				"There should have been an Acl entry for ObjectIdentity "
						+ object);

		return map.get(object);
	}

	public Acl readAclById(ObjectIdentity object) throws NotFoundException {
		return readAclById(object, null);
	}

	public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects)
			throws NotFoundException {
		return readAclsById(objects, null);
	}

	public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects,
			List<Sid> sids) throws NotFoundException {
		Map<ObjectIdentity, Acl> result = new HashMap<ObjectIdentity, Acl>();

		for (ObjectIdentity object : objects) {
			MutableAcl acl = cache.getFromCache(object);
			if (acl != null) {
				_logger.info("Using acl value in cache " + acl);
				result.put(object, acl);
			} else {
				/*
				 * The concept is that each secure domain object has an
				 * associated ACL that defines who can do what with that domain
				 * object
				 * 
				 * Create an object identity for the File, check the permissions
				 * flag to set the ACL here we work with owner and admin
				 * permissions are access, admin, create, delete, read, write
				 * inject Admin User Role
				 */
				String uri = (String) object.getIdentifier();

				/*
				 * We can't inject the repository because it references,
				 * indirectly, so is not finalized at the point of auto
				 * injection. Instead we recover from the application Context.
				 */
				if (repository == null) {
					repository = (Repository) applicationContext
							.getBean("Repository");
				}

				PageACL pageAcl = repository.getPageACL(uri);
				if (pageAcl == null) {
					throw new NotFoundException("could not find it");
				}

				long perms = pageAcl.getPermissions();

				ObjectIdentity fileId = new ObjectIdentityImpl(String.class,
						object.getIdentifier());

				// Owner Acl
				CumulativePermission ownerPermission = new CumulativePermission();
				if (NodePermission.OWNER_WRITE.isSet(perms)) {
					ownerPermission.set(BasePermission.WRITE);
				}
				if (NodePermission.OWNER_READ.isSet(perms)) {
					ownerPermission.set(BasePermission.READ);
				}
				if (NodePermission.OWNER_CREATE.isSet(perms)) {
					ownerPermission.set(BasePermission.CREATE);
				}
				if (NodePermission.OWNER_DELETE.isSet(perms)) {
					ownerPermission.set(BasePermission.DELETE);
				}
				acl = new SimpleAclImpl(fileId,
						new ArrayList<AccessControlEntry>());
				List<AccessControlEntry> entries = acl.getEntries();
				entries.add(new AccessControlEntryImpl("aceOwner", acl,
						new PrincipalSid(pageAcl.getOwner()), ownerPermission,
						true, true, true));

				// Group Permissions
				CumulativePermission groupPermission = new CumulativePermission();
				if (NodePermission.GROUP_WRITE.isSet(perms)) {
					groupPermission.set(BasePermission.WRITE);
				}
				if (NodePermission.GROUP_READ.isSet(perms)) {
					groupPermission.set(BasePermission.READ);
				}
				if (NodePermission.GROUP_CREATE.isSet(perms)) {
					groupPermission.set(BasePermission.CREATE);
				}
				if (NodePermission.GROUP_DELETE.isSet(perms)) {
					groupPermission.set(BasePermission.DELETE);
				}
				entries.add(new AccessControlEntryImpl("aceGroup", acl,
						new GrantedAuthoritySid(pageAcl.getGroup()),
						groupPermission, true, true, true));

				// Other Permissions
				CumulativePermission otherPermission = new CumulativePermission();
				if (NodePermission.OTHER_WRITE.isSet(perms)) {
					otherPermission.set(BasePermission.WRITE);
				}
				if (NodePermission.OTHER_READ.isSet(perms)) {
					otherPermission.set(BasePermission.READ);
				}
				if (NodePermission.OTHER_CREATE.isSet(perms)) {
					otherPermission.set(BasePermission.CREATE);
				}
				if (NodePermission.OTHER_DELETE.isSet(perms)) {
					otherPermission.set(BasePermission.DELETE);
				}
				// need default group
				entries.add(new AccessControlEntryImpl("aceOther", acl,
						new GrantedAuthoritySid("*"), otherPermission, true,
						true, true));

				cache.putInCache(acl);
				result.put(object, acl);
			}
		}

		return result;
	}

	public List<ObjectIdentity> findChildren(ObjectIdentity arg0) {
		return null;
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		this.applicationContext = applicationContext;

	}

}